嗨嗨!大家好!歡迎來到 Rust 三十天挑戰的第十天!
經過前九天的學習,我們已經掌握了 Rust 的基礎語法、所有權系統、錯誤處理等核心概念。今天我們要學習一個能讓程式碼變得更加靈活和可重用的重要特性:泛型 (Generics)。
如果說前面學的是如何寫出「正確」的 Rust 程式碼,那麼泛型就是教我們如何寫出「優雅」的程式碼。想像一下,如果每次需要處理不同型別的資料時,就要複製貼上一堆相似的程式碼,那會有多麻煩?泛型就是解決這個問題的利器!
老實說,剛開始接觸泛型時,我覺得那些尖括號 <T>
看起來很嚇人,感覺像是在學某種神秘的數學符號。但當我理解了泛型的本質——它只是讓我們寫出「適用於多種型別」的程式碼的方式——一切就變得清晰了。今天讓我們一起揭開泛型的神秘面紗!
讓我們先看一個沒有泛型的例子,體會一下重複程式碼的痛苦:
// 找出 i32 向量中的最大值
fn largest_i32(list: &[i32]) -> &i32 {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
// 找出 char 向量中的最大值
fn largest_char(list: &[char]) -> &char {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest_i32(&number_list);
println!("最大的數字是 {}", result);
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest_char(&char_list);
println!("最大的字元是 {}", result);
}
你有沒有發現,除了型別不同,這兩個函式的邏輯完全一樣?這就是重複程式碼的典型案例。
使用泛型,我們可以將上面的兩個函式合併成一個:
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let number_list = vec![34, 50, 25, 100, 65];
let result = largest(&number_list);
println!("最大的數字是 {}", result);
let char_list = vec!['y', 'm', 'a', 'q'];
let result = largest(&char_list);
println!("最大的字元是 {}", result);
}
厲害吧!一個函式就解決了所有可比較型別的最大值查找問題。這就是泛型的威力!非常的優雅!
在 Rust 中,泛型型別參數通常使用大寫字母命名:
// 常見的泛型型別參數名稱
T // Type(型別)- 最常用的泛型參數
E // Error(錯誤)- 通常用於 Result<T, E>
K // Key(鍵)- 通常用於 HashMap<K, V>
V // Value(值)- 通常用於 HashMap<K, V>
R // Result(結果)- 函式回傳型別
I // Iterator(迭代器)
// 函式中的泛型
fn function_name<T>(parameter: T) -> T {
// 函式體
}
// 結構中的泛型
struct StructName<T> {
field: T,
}
// 列舉中的泛型
enum EnumName<T> {
Variant(T),
}
// 方法中的泛型
impl<T> StructName<T> {
fn method(&self) -> &T {
&self.field
}
}
// 簡單的泛型函式
fn print_value<T: std::fmt::Display>(value: T) {
println!("值是:{}", value);
}
// 交換兩個值的函式
fn swap<T>(a: &mut T, b: &mut T) {
std::mem::swap(a, b);
}
fn main() {
print_value(42);
print_value("Hello");
print_value(3.14);
let mut x = 5;
let mut y = 10;
println!("交換前:x = {}, y = {}", x, y);
swap(&mut x, &mut y);
println!("交換後:x = {}, y = {}", x, y);
}
// 擁有多個型別參數的函式
fn create_pair<T, U>(first: T, second: U) -> (T, U) {
(first, second)
}
// 型別轉換函式
fn convert_and_combine<T, U, R>(
a: T,
b: U,
converter: fn(T, U) -> R
) -> R {
converter(a, b)
}
fn main() {
let pair1 = create_pair(42, "hello");
let pair2 = create_pair(3.14, true);
let pair3 = create_pair("world", vec![1, 2, 3]);
println!("配對1:{:?}", pair1);
println!("配對2:{:?}", pair2);
println!("配對3:{:?}", pair3);
// 使用轉換函式
let result = convert_and_combine(
10,
20,
|a, b| format!("{} + {} = {}", a, b, a + b)
);
println!("轉換結果:{}", result);
}
// 一個可以儲存任何型別的容器
#[derive(Debug)]
struct Container<T> {
value: T,
}
impl<T> Container<T> {
fn new(value: T) -> Self {
Container { value }
}
fn get(&self) -> &T {
&self.value
}
fn set(&mut self, value: T) {
self.value = value;
}
// 轉換成另一種型別的容器
fn map<U, F>(self, f: F) -> Container<U>
where
F: FnOnce(T) -> U,
{
Container::new(f(self.value))
}
}
fn main() {
let mut int_container = Container::new(42);
println!("整數容器:{:?}", int_container);
let string_container = Container::new(String::from("Hello"));
println!("字串容器:{:?}", string_container);
// 修改值
int_container.set(100);
println!("修改後:{:?}", int_container);
// 型別轉換
let string_from_int = int_container.map(|x| format!("數字:{}", x));
println!("轉換後:{:?}", string_from_int);
}
#[derive(Debug)]
struct Point<T, U> {
x: T,
y: U,
}
impl<T, U> Point<T, U> {
fn new(x: T, y: U) -> Self {
Point { x, y }
}
fn x(&self) -> &T {
&self.x
}
fn y(&self) -> &U {
&self.y
}
}
// 只為特定型別組合實現方法
impl Point<f64, f64> {
fn distance_from_origin(&self) -> f64 {
(self.x.powi(2) + self.y.powi(2)).sqrt()
}
}
// 混合型別的方法
impl<T, U> Point<T, U> {
fn mixup<V, W>(self, other: Point<V, W>) -> Point<T, W> {
Point {
x: self.x,
y: other.y,
}
}
}
fn main() {
let int_point = Point::new(5, 10);
let float_point = Point::new(1.0, 4.0);
let mixed_point = Point::new(5, 4.0);
println!("整數座標:{:?}", int_point);
println!("浮點數座標:{:?}", float_point);
println!("混合座標:{:?}", mixed_point);
// 只有 f64 座標能計算距離
println!("距離原點:{:.2}", float_point.distance_from_origin());
// 混合不同的點
let result = int_point.mixup(float_point);
println!("混合結果:{:?}", result);
}
我們其實已經在前面的學習中遇到過泛型列舉了!
// Option<T> 是標準函式庫中的泛型列舉
enum Option<T> {
Some(T),
None,
}
// Result<T, E> 也是泛型列舉
enum Result<T, E> {
Ok(T),
Err(E),
}
#[derive(Debug)]
enum Message<T> {
Quit,
Move { x: i32, y: i32 },
Write(String),
Data(T), // 可以攜帶任何型別的資料
}
impl<T> Message<T> {
fn process(&self)
where
T: std::fmt::Debug,
{
match self {
Message::Quit => println!("收到退出訊息"),
Message::Move { x, y } => println!("移動到 ({}, {})", x, y),
Message::Write(text) => println!("寫入:{}", text),
Message::Data(data) => println!("資料:{:?}", data),
}
}
}
// 多型別參數的列舉
#[derive(Debug)]
enum Either<L, R> {
Left(L),
Right(R),
}
impl<L, R> Either<L, R> {
fn is_left(&self) -> bool {
matches!(self, Either::Left(_))
}
fn is_right(&self) -> bool {
matches!(self, Either::Right(_))
}
// 轉換左側值
fn map_left<T, F>(self, f: F) -> Either<T, R>
where
F: FnOnce(L) -> T,
{
match self {
Either::Left(l) => Either::Left(f(l)),
Either::Right(r) => Either::Right(r),
}
}
// 轉換右側值
fn map_right<T, F>(self, f: F) -> Either<L, T>
where
F: FnOnce(R) -> T,
{
match self {
Either::Left(l) => Either::Left(l),
Either::Right(r) => Either::Right(f(r)),
}
}
}
fn main() {
let messages = vec![
Message::Quit,
Message::Move { x: 10, y: 20 },
Message::Write(String::from("Hello")),
Message::Data(42),
Message::Data(vec![1, 2, 3]),
];
for msg in messages {
msg.process();
}
// Either 的使用
let left: Either<i32, String> = Either::Left(42);
let right: Either<i32, String> = Either::Right(String::from("hello"));
println!("left is left: {}", left.is_left());
println!("right is right: {}", right.is_right());
// 轉換操作
let doubled = Either::Left(21).map_left(|x| x * 2);
let uppercase = Either::Right(String::from("world")).map_right(|s| s.to_uppercase());
println!("雙倍:{:?}", doubled);
println!("大寫:{:?}", uppercase);
}
#[derive(Debug)]
struct Pair<T> {
first: T,
second: T,
}
// 為所有型別 T 實現的方法
impl<T> Pair<T> {
fn new(first: T, second: T) -> Self {
Self { first, second }
}
fn into_tuple(self) -> (T, T) {
(self.first, self.second)
}
}
// 只為特定型別實現的方法
impl Pair<i32> {
fn sum(&self) -> i32 {
self.first + self.second
}
}
// 為實現了特定 trait 的型別實現方法
impl<T: std::fmt::Display + PartialOrd> Pair<T> {
fn cmp_display(&self) {
if self.first >= self.second {
println!("最大的成員是 first = {}", self.first);
} else {
println!("最大的成員是 second = {}", self.second);
}
}
}
fn main() {
let int_pair = Pair::new(10, 20);
let string_pair = Pair::new(String::from("hello"), String::from("world"));
println!("整數對:{:?}", int_pair);
println!("字串對:{:?}", string_pair);
// 只有 i32 配對有 sum 方法
println!("總和:{}", int_pair.sum());
// 只有實現了 Display + PartialOrd 的型別才有這個方法
int_pair.cmp_display();
string_pair.cmp_display();
}
#[derive(Debug)]
struct Point<T> {
x: T,
y: T,
}
impl<T> Point<T> {
// 方法本身也可以有泛型參數
fn distance_to<U>(&self, other: &Point<U>) -> f64
where
T: Into<f64> + Copy,
U: Into<f64> + Copy,
{
let dx = self.x.into() - other.x.into();
let dy = self.y.into() - other.y.into();
(dx * dx + dy * dy).sqrt()
}
// 型別轉換方法
fn convert<U, F>(self, converter: F) -> Point<U>
where
F: Fn(T) -> U,
{
Point {
x: converter(self.x),
y: converter(self.y),
}
}
}
fn main() {
let int_point = Point { x: 3, y: 4 };
let float_point = Point { x: 1.0, y: 2.0 };
println!("距離:{:.2}", int_point.distance_to(&float_point));
// 型別轉換
let string_point = int_point.convert(|x| format!("值:{}", x));
println!("轉換後:{:?}", string_point);
}
到目前為止,你可能已經注意到我們經常使用 where
子句和 :
語法。這就是 Trait Bounds,用來約束泛型參數必須實現特定的 trait。
use std::fmt::Display;
// 直接在泛型參數上指定約束
fn print_and_return<T: Display>(value: T) -> T {
println!("值:{}", value);
value
}
// 多個約束
fn compare_and_print<T: Display + PartialOrd>(a: T, b: T) {
if a > b {
println!("{} 大於 {}", a, b);
} else if a < b {
println!("{} 小於 {}", a, b);
} else {
println!("{} 等於 {}", a, b);
}
}
// 使用 where 子句(讓泛型所代表的定義更清楚)
fn complex_function<T, U>(a: T, b: U) -> String
where
T: Display + Clone,
U: Display + Into<String>,
{
format!("a: {}, b: {}, a_cloned: {}", a, a.clone(), b.into())
}
fn main() {
let number = print_and_return(42);
let text = print_and_return("Hello");
compare_and_print(10, 20);
compare_and_print("apple", "banana");
let result = complex_function(42, String::from("world"));
println!("複雜函式結果:{}", result);
}
Rust 的泛型使用「單態化」(monomorphization) 技術,這意味著泛型程式碼在編譯時會被展開成具體型別的版本,沒有執行時開銷!
// 這個泛型函式...
fn add<T: std::ops::Add<Output = T>>(a: T, b: T) -> T {
a + b
}
// 在編譯時會被展開成類似這樣:
// fn add_i32(a: i32, b: i32) -> i32 { a + b }
// fn add_f64(a: f64, b: f64) -> f64 { a + b }
// fn add_string(a: String, b: String) -> String { a + b }
fn main() {
let int_sum = add(5, 10); // 呼叫 add_i32
let float_sum = add(3.14, 2.86); // 呼叫 add_f64
let string_sum = add( // 呼叫 add_string
String::from("Hello, "),
String::from("World!")
);
println!("整數和:{}", int_sum);
println!("浮點數和:{}", float_sum);
println!("字串和:{}", string_sum);
}
這種設計讓 Rust 的泛型既靈活又高效,完全沒有動態分派的開銷!
#[derive(Debug)]
struct Config<T, U> {
host: T,
port: U,
debug: bool,
}
struct ConfigBuilder<T, U> {
host: Option<T>,
port: Option<U>,
debug: bool,
}
impl<T, U> ConfigBuilder<T, U> {
fn new() -> Self {
ConfigBuilder {
host: None,
port: None,
debug: false,
}
}
fn host(mut self, host: T) -> Self {
self.host = Some(host);
self
}
fn port(mut self, port: U) -> Self {
self.port = Some(port);
self
}
fn debug(mut self, debug: bool) -> Self {
self.debug = debug;
self
}
fn build(self) -> Result<Config<T, U>, String> {
let host = self.host.ok_or("Host is required")?;
let port = self.port.ok_or("Port is required")?;
Ok(Config {
host,
port,
debug: self.debug,
})
}
}
fn main() {
let config = ConfigBuilder::new()
.host("localhost")
.port(8080)
.debug(true)
.build()
.unwrap();
println!("設定:{:?}", config);
}
trait Factory<T> {
fn create(&self) -> T;
}
struct NumberFactory;
struct StringFactory;
impl Factory<i32> for NumberFactory {
fn create(&self) -> i32 {
42
}
}
impl Factory<String> for StringFactory {
fn create(&self) -> String {
"Hello, Factory!".to_string()
}
}
fn create_item<T, F: Factory<T>>(factory: &F) -> T {
factory.create()
}
fn main() {
let number = create_item(&NumberFactory);
let text = create_item(&StringFactory);
println!("數字:{}", number);
println!("文字:{}", text);
}
struct RangeIterator<T> {
current: T,
end: T,
step: T,
}
impl<T> RangeIterator<T>
where
T: Copy + PartialOrd + std::ops::Add<Output = T>,
{
fn new(start: T, end: T, step: T) -> Self {
RangeIterator {
current: start,
end,
step,
}
}
}
impl<T> Iterator for RangeIterator<T>
where
T: Copy + PartialOrd + std::ops::Add<Output = T>,
{
type Item = T;
fn next(&mut self) -> Option<Self::Item> {
if self.current < self.end {
let current = self.current;
self.current = self.current + self.step;
Some(current)
} else {
None
}
}
}
fn main() {
// 整數範圍
let int_range = RangeIterator::new(0, 10, 2);
for num in int_range {
print!("{} ", num);
}
println!();
// 浮點數範圍
let float_range = RangeIterator::new(0.0, 5.0, 0.5);
for num in float_range {
print!("{:.1} ", num);
}
println!();
}
// ✅ 好的命名
struct Cache<Key, Value> {
data: std::collections::HashMap<Key, Value>,
}
struct Parser<Input, Output, Error> {
// ...
}
// ❌ 不好的命名
struct Cache<T, U> { // T 和 U 沒有表達清楚意圖
data: std::collections::HashMap<T, U>,
}
// ✅ 好:只在需要時添加約束
fn process_data<T>(data: Vec<T>) -> Vec<T> {
// 不需要約束的操作
data
}
fn print_data<T: std::fmt::Display>(data: &[T]) {
// 需要 Display 約束才能印出
for item in data {
println!("{}", item);
}
}
// ❌ 不好:過度約束
fn store_data<T: Clone + Debug + PartialEq + Ord>(data: T) -> T {
// 實際上只需要移動資料,不需要這麼多約束
data
}
// 當只有一個合理的型別選擇時,使用關聯型別
trait Iterator {
type Item; // 關聯型別
fn next(&mut self) -> Option<Self::Item>;
}
// 而不是泛型參數
trait BadIterator<T> { // 這樣會讓同一個型別可以實現多次
fn next(&mut self) -> Option<T>;
}
// 編譯時多態(泛型)- 零成本
fn generic_add<T: std::ops::Add<Output = T>>(a: T, b: T) -> T {
a + b // 編譯時就知道具體的 + 實現
}
// 執行時多態(trait object)- 有虛擬呼叫成本
fn dynamic_add(a: Box<dyn std::ops::Add<Output = i32>>, b: i32) -> i32 {
// 執行時透過虛擬函式表查找方法
unimplemented!() // 這只是示例,實際上 Add trait 不能這樣用
}
// 實際效能比較
use std::time::Instant;
fn performance_test() {
let iterations = 10_000_000;
// 泛型版本
let start = Instant::now();
let mut sum = 0;
for i in 0..iterations {
sum = generic_add(sum, i);
}
let generic_time = start.elapsed();
println!("泛型耗時: {:?}, 結果: {}", generic_time, sum);
}
今天我們深入學習了 Rust 的泛型系統:
核心概念:
<T>
在函式、結構、列舉、方法中的使用:
和 where
子句約束泛型參數實用技巧:
設計模式:
最佳實務:
為什麼泛型很重要?
泛型是現代程式語言的重要特性,Rust 的泛型系統在保持零成本的同時,提供了極高的表達力和安全性。掌握泛型將讓你能夠寫出更加抽象、更可重用、但同樣高效的程式碼。
為了鞏固今天的學習,嘗試實作一個通用的緩存系統:
功能需求:
get
, put
, remove
操作泛型要求:
K
必須可比較和雜湊V
必須可克隆技術提示:
use std::collections::HashMap;
use std::hash::Hash;
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
struct CacheEntry<V> {
value: V,
created_at: Instant,
last_accessed: Instant,
ttl: Option<Duration>,
}
struct Cache<K, V, E = NoExpiration>
where
K: Hash + Eq + Clone,
V: Clone,
E: ExpirationPolicy<V>,
{
data: HashMap<K, CacheEntry<V>>,
capacity: usize,
expiration_policy: E,
// 統計資訊
hits: u64,
misses: u64,
}
trait ExpirationPolicy<V> {
fn is_expired(&self, entry: &CacheEntry<V>) -> bool;
fn on_access(&self, entry: &mut CacheEntry<V>);
}
struct NoExpiration;
struct TimeBasedExpiration {
default_ttl: Duration,
}
// 你來實現這些功能!
這個挑戰將讓你綜合運用泛型的各種特性:型別參數、trait bounds、條件實現、關聯型別等。重點是設計一個既靈活又易用的泛型 API。
明天我們將學習 Traits(特徵),這是 Rust 實現多型和程式碼共享的核心機制。Traits 與泛型結合使用,能讓我們寫出更加抽象和強大的程式碼!
如果在實作過程中遇到任何問題,歡迎在留言區討論。泛型是一個需要多練習才能熟練的概念,但一旦掌握,它會成為你最強大的工具之一!
我們明天見!